package org.example.services.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.extern.slf4j.Slf4j;
import org.example.Exception.BusinessException;
import org.example.Exception.RollBackException;
import org.example.constant.Header;
import org.example.constant.RoleEnum;
import org.example.dao.MenuDao;
import org.example.dao.UserDao;
import org.example.domain.CheckCode;
import org.example.domain.LoginUser;
import org.example.domain.UserToken;
import org.example.domain.response.ResponseResult;
import org.example.domain.User;
import org.example.domain.response.ResponseResultEnum;
import org.example.services.LoginService;
import org.example.utils.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@Component
@Transactional  //事务回滚
public class LoginServiceImpl implements LoginService{

    @Resource   //使用Autowired会有警告
    private UserDao userDao;
    @Resource
    private MenuDao menuDao;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private MailUtils mailUtils;
    @Autowired
    private RedisCache redisCache;

    //登出logout 直接前端清除token，后端等待token过期
    //如果使用redis,可以清除redis中的用户数据

    //注册
    @Override
    public ResponseResult register(User user, String uuid) {
        try {
            //判断用户名是否已经被使用
            User userName = userDao.getUserByUserName(user.getUsername());
            if(userName != null){
                log.info("{}用户名已存在",user.getUsername());
                return ResponseResult.failed("用户名存在");
            }

            String code = redisCache.getCacheObject(uuid);
            redisCache.deleteObject(uuid);
            if(code == null){
                log.info("注册：验证码过时");
                return ResponseResult.failed("验证码超时");
            }
            if(!code.equalsIgnoreCase(user.getCheckCode())){
                log.info("注册：验证码校验错误");
                return ResponseResult.failed("验证码错误");
            }
            //将前端加密的数据解，deCryptPassword解密后的密码
            log.info("密码解密前：{}",user.getPassword());
            String deCryptPassword = deCrypt(user.getPassword());
            //将解密后的密码加密，然后存入数据库，次加密算法不可逆
            user.setPassword(passwordEncoder.encode(deCryptPassword));
            userDao.insert(user);
            //通过返回的userId插入默认的普通用户角色
            userDao.insertUserRole(user.getUserId(), RoleEnum.User.getRoleId());
            log.info(user.getUsername() + "用户注册成功");
            return ResponseResult.success("注册成功，请登录",null);
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("用户注册失败");
            throw new RollBackException("用户注册失败");
        }
    }

    //用户名登录
    @Override
    public ResponseResult loginByUserName(User user, String uuid){

        String verifyCheckCode = redisCache.getCacheObject(uuid);
//        redisCache.deleteObject(uuid);
        if(verifyCheckCode == null){
            log.info("登录：验证码过时");
            return ResponseResult.failed("验证码超时");
        }
        if(!verifyCheckCode.equalsIgnoreCase(user.getCheckCode())){
            log.info("登录：验证码校验错误" + "getCheckCode:" + user.getCheckCode() + " verifyCheckCode:" + verifyCheckCode);
            return ResponseResult.failed("验证码错误");
        }

        //将前端加密的数据解，deCryptPassword解密后的密码
        System.out.println(user.getPassword() + "=======================================");
        String deCryptPassword = deCrypt(user.getPassword());
        user.setPassword(deCryptPassword);

        Authentication authenticationToken =
                new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);

        if(Objects.isNull(authenticate)){
            log.info(user.getUsername() + "用户查询失败");
            return ResponseResult.failed("登录失败，请检查账号或密码");
        }
        //LoginUser对象，内包含userId
        //查出的数据可以存入redis，然后在token认证过滤器中使用redis，加快速度
        LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
        User userRes = loginUser.getUser();

        //把完整的用户信息存入redis  userId作为key
        Integer userId = userRes.getUserId();   //获取查出id
        //设置token过期事件为3小时
        redisCache.setCacheObject("login:"+userId,loginUser, 3, TimeUnit.HOURS);
        //通过用户ID,用户名，角色生成Token
        String username = userRes.getUsername();//获取查出用户名
        String role = userRes.getRole();    //获取查出role
        String token = TokenUtils.generate(new UserToken(userId,username,role));
        userRes.setToken(token);
        //把对应的设置为空，让前端得不到
        userRes.setPassword(null);
        userRes.setCheckCode(null);
        log.info(user.getUsername() + "用户登录成功");
        return ResponseResult.success("登录成功",userRes);
    }


    //获取验证码图片
    @Override
    public ResponseResult getCheckCode(HttpServletRequest request){
        try {
            String requestURI = request.getRequestURI();
            //从redis中获取30分钟内是否已经发送了邮箱验证码
            String redisKey = "getCheckCode:" + requestURI;
            String mailCode = redisCache.getCacheObject(redisKey);
            if(!Objects.isNull(mailCode)){
                log.info("{}在1分钟内重复获取验证码",requestURI);
                return ResponseResult.failed("获取验证码需要间隔1分钟，请稍后重试");
            }

            //画图工具类
            CheckCodeUtils imageCode = new CheckCodeUtils();
            // 获取验证码对应的 base64  编码
            String base64 = CheckCodeUtils.getBase64(imageCode.getImage());

            // 获取对应的 验证码 code
            String code = imageCode.getCode();

            // 生成 UUID
            String uuid = UUID.randomUUID().toString();

            // 封装 获取的 验证码相关的数据 到 验证码对象中，并响应
            CheckCode checkCode = new CheckCode();
            checkCode.setUUID(uuid);
            checkCode.setImg(base64);
//            checkCode.setCode(code);

            // 将验证码的信息保存到 redis中,并设置 有效时间3分钟！
            redisCache.setCacheObject(uuid,code, 60 * 3, TimeUnit.SECONDS);
            // 将验证码的信息保存到 redis中,并设置 有效时间1分钟！,通过请求的IP地址，防止重一直刷获取验证码
            redisCache.setCacheObject(redisKey,code, 60, TimeUnit.SECONDS);
            // 将封装好的验证码对象响应给前端
            log.info("获取验证码成功，验证码：" + code + "，有效时间3分钟");
            return ResponseResult.success("获取成功，有效时间3分钟" ,checkCode);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("验证码获取失败");
            return ResponseResult.failed("获取验证码失败");
        }
    }
    //通过邮箱获取验证码
    @Override
    public ResponseResult getCheckCodeByMail(String mailNumber){

        //从redis中获取30分钟内是否已经发送了邮箱验证码
        String redisKey = "getCheckCodeByMail:" + mailNumber;
        String mailCode = redisCache.getCacheObject(redisKey);
        if(!Objects.isNull(mailCode)){
            log.info("{}邮箱在30分钟内重复获取验证码",mailNumber);
            return ResponseResult.failed("邮箱获取验证码需要间隔30分钟，请稍后重试");
        }

        String title = "程序猿邮箱注册";
        //获得验证码6位
        String code = CheckCodeUtils.generateVerifyCode(6);
        //由于发送邮箱的方法反应时间太长，这里采用多线程方式发送，不会导致页面返回时间超时
        Thread sendMail = new Thread(()->{
            boolean flag = mailUtils.sendCheckCodeEmail(title, mailNumber, code);
        });
        sendMail.run();
        // 生成 UUID
        String uuid = UUID.randomUUID().toString();
        // 封装 获取的 验证码相关的数据 到 验证码对象中，并响应
        CheckCode checkCode = new CheckCode();
        checkCode.setUUID(uuid);
        log.info("将要发送邮箱的验证码存入redis");
        redisCache.setCacheObject(uuid,code, 60 * 3, TimeUnit.SECONDS);
        // 将验证码的信息保存到 redis中,并设置 有效时间30分钟！,防止重复刷接口调用三方接口
        redisCache.setCacheObject(redisKey,code, 30, TimeUnit.MINUTES);
        log.info(mailNumber + "邮箱获取验证，验证码：" + code);
        return ResponseResult.success("请登录邮箱查看验证码，60秒后请刷新重试",checkCode);

    }
    //验证验证码  用户名登录
    @Override
    public ResponseResult verifyCheckCode(User user, String uuid){
        String codeRes = redisCache.getCacheObject(uuid);
//        redisCache.deleteObject(uuid);
        if(codeRes == null){
            log.info(user.getUsername() + "的验证码过时");
            return ResponseResult.failed(ResponseResultEnum.VALIDATE_FAILED.getCode(),"验证码过时");
        }
        if(codeRes.equalsIgnoreCase(user.getCheckCode())){
            //验证码通过
            log.info(user.getUsername() + "验证码校验成功");
            return null;
        }else{
            log.info(user.getUsername() + "用户的验证码校验失败");
            return ResponseResult.failed(ResponseResultEnum.VALIDATE_FAILED.getCode(),"验证码错误");
        }
    }

    //通过邮箱登录
    @Override
    public ResponseResult loginByMail(User user,String uuid) {
        try {
            String code = redisCache.getCacheObject(uuid);
//            redisCache.deleteObject(uuid);
            if(code == null){
                log.info("邮箱验证码过时");
                return ResponseResult.failed("验证码过时，请重新获取验证码");
            }
            if(!code.equalsIgnoreCase(user.getCheckCode())){
                log.info("邮箱验证码校验失败");
                return ResponseResult.failed("邮箱验证码校验失败");
            }
            //校验邮箱验证码成功，进行登录
            //通过邮箱获取用户信息
            LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(User::getMail,user.getMail());
            User userRes = userDao.selectOne(lambdaQueryWrapper);

            //通过用户信息获取权限信息
            Integer userId = userRes.getUserId();   //获取查出id
            List<String> grantedAuthority = menuDao.getGrantedAuthority(userId);

            //把完整的用户信息存入redis  userId作为key
            LoginUser loginUser = new LoginUser(userRes,grantedAuthority);
            //设置token过期事件为3小时
            redisCache.setCacheObject("login:"+userId,loginUser, 3, TimeUnit.HOURS);

            //查询并设置用户角色
            String role = userDao.getRoleByUserId(userId);
            userRes.setRole(role);
            //通过用户ID,用户名，角色生成Token
            String username = userRes.getUsername();//获取查出用户名
            String token = TokenUtils.generate(new UserToken(userId,username,role));
            userRes.setToken(token);
            //把对应的设置为空，让前端得不到
            userRes.setPassword(null);
            userRes.setCheckCode(null);
            log.info(userRes.getUsername() + "用户通过邮箱登录成功");
            return ResponseResult.success("邮箱登录成功",userRes);
        } catch (Exception e) {
            log.info(user.getMail() + "邮箱的登录失败");
            return ResponseResult.failed("邮箱的登录失败");
        }
    }

    //登出
    @Override
    public ResponseResult logout(HttpServletRequest request) {
        String token = request.getHeader(Header.Token);
        if(token != null){
            UserToken userToken = TokenUtils.getUserToken(token);
            String key = "login:" + userToken.getUserId();
            if(redisCache.deleteObject(key)){
                return ResponseResult.success("登出成功",null);
            }
            return ResponseResult.failed("登出失败");
        }
        return ResponseResult.failed(ResponseResultEnum.AUTH_ERROR.getCode(), "token过时");
    }

//    //前端密码解密
    public static String deCrypt(String enCryptText) {
        try {
            //密钥
            final String PRIVATE_KEY_STR = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY" +
                    "7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN" +
                    "PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA" +
                    "kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow" +
                    "cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv" +
                    "DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh" +
                    "YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3" +
                    "UP8iWi1Qw0Y=";
            //解密
            String dnCryptText = CsoftSecurityUtil.decryptRSADefault(PRIVATE_KEY_STR, enCryptText);
            log.info("密码解密成功，解密后密码为：" + dnCryptText);
            return dnCryptText;
        } catch (Exception e) {
            e.printStackTrace();
            log.warn("前端传来的密码解密失败");
            throw new BusinessException("操作失败，请重试");

        }

    }
}
